package net.j4love.nio.channels;

import org.junit.Assert;
import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

/**
 * @author he peng
 * @create 2018/6/4 17:01
 * @see
 */
public class SelectableChannelTest {

    // 测试 SelectableChannel , 用 ServerSocketChannel 进行测试
    // 可选择的通道和选择器是紧密关联的 , 选择器的存在支持着可选择通道的工作 ， 多路复用 I/O 模型

    final SocketAddress socketAddress = new InetSocketAddress("127.0.0.1" , 12345);

    private ServerSocketChannel openChannel() throws IOException {
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        Assert.assertNotNull(ssChannel);
        Assert.assertTrue(ssChannel.isOpen());
        return ssChannel;
    }

    private ServerSocketChannel bind(SocketAddress socketAddress) throws Exception {
        ServerSocketChannel ssChannel = openChannel();
        ssChannel.bind(socketAddress);
        ssChannel.configureBlocking(false);
        return ssChannel;
    }

    @Test
    public void openTest() throws Exception {
        ServerSocketChannel ssChannel = openChannel();
        Assert.assertNotNull(ssChannel);
        Assert.assertTrue(ssChannel.isOpen());
        System.out.println("server socket channel is open");
    }

    @Test
    public void validOpsTest() throws Exception {
        ServerSocketChannel ssChannel = bind(socketAddress);

        // 返回该通道支持的操作的操作集
        System.out.println("validOps -> " + ssChannel.validOps());
    }

    @Test
    public void isRegisteredTest() throws Exception {
        ServerSocketChannel ssChannel = bind(socketAddress);

        // 检测该通道是否注册到了任何的选择器 （selector） 上
        System.out.println("channel isRegistered -> " + ssChannel.isRegistered());
    }

    private Map<ServerSocketChannel , Selector> register() throws Exception {
        ServerSocketChannel ssChannel = bind(socketAddress);
        Selector selector = Selector.open();

        // 注册的时候通道如果是阻塞模式抛出异常 , 注册的选择键如果不是通道支持的选择键类型抛出异常
        SelectionKey sk = ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        Assert.assertTrue(sk.channel() == ssChannel);
        Assert.assertTrue(sk.selector() == selector);
        System.out.println("registered selection key interestOps -> " + sk.interestOps());
        System.out.println("registered selection key readyOps -> " + sk.readyOps());
        System.out.println("registered selection key isValid -> " + sk.isValid());
        System.out.println("registered selection key isAcceptable -> " + sk.isAcceptable());
        System.out.println("registered selection key isConnectable -> " + sk.isConnectable());
        System.out.println("registered selection key isReadable -> " + sk.isReadable());
        System.out.println("registered selection key isWritable -> " + sk.isWritable());
        HashMap<ServerSocketChannel, Selector> map = new HashMap<>(1);
        map.put(ssChannel , selector);
        System.out.println("========================= register() over =========================");
        return map;
    }

    // 通道注册测试
    @Test
    public void registerTest() throws Exception {
        Map<ServerSocketChannel, Selector> registerMap = register();
        Assert.assertNotNull(registerMap);
        Assert.assertEquals(1 , registerMap.size());
    }

    @Test
    public void selectTest() throws Exception {
        Map<ServerSocketChannel, Selector> registerMap = register();
        registerMap.forEach((ssChannel , selector) -> {
            try {
                int num = selector.select(3000);
                System.out.println("selected num -> " + num);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 使用给定的选择器检索表示通道注册的键。
            SelectionKey sk = ssChannel.keyFor(selector);
            System.out.println("channel keyFor -> " + sk.interestOps());
            System.out.println("selector keys -> " + selector.keys());
            System.out.println("selector selectedKeys -> " + selector.selectedKeys());
        });

        // SelectionKey 封装了 通道(channel)、选择器(selector)、通道感兴趣的事件、实际上处于就绪状态的事件

        // 1. 打开服务端 ， 服务端注册到选择器中 ， 接受客户端连接 ， 将接受的连接通道(以及该通道感兴趣的事件)注册到选择器上
        // 1. 选择器选择一组处于就绪状态的选择键 （selection key）, 选择键中有就绪的通道和就绪的事件
    }
}
